#pragma once

#include <QtXml/QDomDocument>

#include <boost/optional.hpp>
#include <boostext/smart_ptr.hpp>
#include <engine.core/factory.hpp>
#include <engine.core/qt_stl_integration.hpp>

#include "exports.hpp"
#include "Glyph.hpp"
#include "Font.hpp"
#include "WidgetStyle.hpp"




namespace Engine
{
	namespace Ui
	{
		class font_missing_exception : public boostext::exception
		{
		public:

			font_missing_exception(const FontDesc& font)
				: boostext::exception(boostext::format("There is no font with the following properties: %1%, %2%px size, %3%px outline, %4% and %5%")
					% font.name
					% font.size
					% font.outline
					% (font.bold ? "bold" : "normal-weight")
					% (font.italic ? "italic" : "normal-type"))
			{}
		};
		///////////////////////////////////////////////////////////////////////////////////////////




		/**
		 * A StyleDefinition contains the texture coordinates for all glyphs of a specific style.
		 * An image, containing those glyphs must be used, along with a style in order to render a Ui.
		 */
		template <typename Font_t, typename Glyph_t>
		class StyleDefinition
		{
		public:

			typedef StyleDefinition<Font_t,Glyph_t>                     StyleDefinition_t;
			typedef boost::shared_ptr<StyleDefinition_t>                ptr;
			typedef QMap<QString,Glyph_t>                               Glyphs;
			typedef QMap<FontDesc,Font_t>                               Fonts;
			typedef std::pair<WidgetStyleIdentifier,WidgetStyle::ptr>   WidgetStylePair;
			typedef std::map<WidgetStyleIdentifier,WidgetStyle::ptr>    WidgetStyles;

		protected:

			/// Name of the texture, this style requires
			QString                     m_texture;

			/// Size of the texture in pixels
			int2                        m_size;

			Glyphs                      m_glyphs;
			Fonts                       m_fonts;
			WidgetStyles                m_widgetStyles;

		public:

			/**
			 * Create an empty StyleDefinition.
			 */
			StyleDefinition()
			{}

			virtual ~StyleDefinition()
			{}



			/**
			 * Load the given Style Definition document.
			 */
			template <typename Parser>
			void load(QString fname)
			{
				// Okay, load the bastard
				QDomDocument doc;
				QFileInfo info(fname);
				QDir path = info.absolutePath();
				QFile f(fname);
				if(!f.open(QIODevice::ReadOnly))
					BOOSTEXT_THROW(boostext::exception(boostext::format("Can't open style definition '%1%': %2%") % fname % f.error()));

				QString errorMsg;
				if(!doc.setContent(&f, false, &errorMsg))
					BOOSTEXT_THROW(boostext::exception(boostext::format("Can't load style definition '%1%', parse error: %2%") % fname % errorMsg));


				QDomElement docElem = doc.documentElement();

				// First, we load the basic ui definition
				QDomNode ui = docElem.namedItem("ui");
				if(ui.isNull())
					BOOSTEXT_THROW(boostext::exception("Missing element"));

				QDomElement uiElem = ui.toElement();

				m_texture = uiElem.attribute("file");
				m_size = int2(uiElem.attribute("w").toInt(), uiElem.attribute("h").toInt());

				QDomElement fontElem = docElem.firstChildElement("font");
				while(!fontElem.isNull())
				{
					add(Font_t(fontElem, path));

					fontElem = fontElem.nextSiblingElement("font");
				}

				QDomElement glyphElem = docElem.firstChildElement("glyph");
				while(!glyphElem.isNull())
				{
					add(Glyph_t(glyphElem, path));
					glyphElem = glyphElem.nextSiblingElement("glyph");
				}

				QDomElement styleElem = docElem.firstChildElement("style");
				if(!styleElem.isNull())
				{
					QString content = styleElem.text();
					Parser parser(this);
					if(!parser.parse(content))
						BOOSTEXT_THROW(boostext::exception(boostext::format("Unable to load style: There are %1% errors") % parser.errors().size()));
				}
			}

			/**
			 * Removes all styles from the style document.
			 */
			void clearStyles()
			{
				m_widgetStyles.clear();
			}



			/**
			 * Get the name of the texture for this style.
			 */
			QString texture() const
			{
				return m_texture;
			}

			/**
			 * Get the size of the texture atlas of this style.
			 */
			int2 size() const
			{
				return m_size;
			}



			/**
			 * Sets the texture name.
			 */
			void setTexture(QString name)
			{
				m_texture = name;
			}

			/**
			 * Set the texture size.
			 */
			void setSize(int2 size)
			{
				m_size = size;
			}



			/**
			 * Test if the given glyph exists.
			 */
			bool hasGlyph(QString name) const
			{
				return m_glyphs.find(name) != m_glyphs.end();
			}

			/**
			 * Test if the given font exists.
			 */
			bool hasFont(const FontDesc& desc) const
			{
				return m_fonts.find(desc) != m_fonts.end();
			}

			/**
			 * Test if the given WidgetStyle exists.
			 */
			bool hasStyle(const WidgetStyleIdentifier& styleId) const
			{
				// TODO: Evaluate if this is the right thing to do
				return m_widgetStyles.find(styleId) != m_widgetStyles.end();
			}




			/**
			 * Obtain the glyph with the given name.
			 */
			const Glyph_t& glyph(QString name) const
			{
				Glyphs::const_iterator i = m_glyphs.find(name);
				if(i == m_glyphs.end())
					BOOSTEXT_THROW(boostext::exception(boostext::format("There is no such glyph: '%1%'") % name));

				return *i;
			}

			/**
			 * Obtain a list of glyphs.
			 */
			Glyphs glyphs(const QStringList& names) const
			{
				Glyphs tmp;

				foreach(QString name, names)
					tmp.insert(name, glyph(name));

				return tmp;
			}

			/**
			 * Obtain a list of glyphs.
			 */
			template <typename Identifier>
			QMap<Identifier,Glyph_t> glyphs(QMap<Identifier,QString> names) const
			{
				QMap<Identifier,Glyph_t> tmp;

				foreach(const Identifier& key, names.keys())
					tmp.insert(key, glyph(names[key]));

				return tmp;
			}

			/**
			 * Get a list of all WidgetStyles.
			 */
			const WidgetStyles& styles() const
			{
				return m_widgetStyles;
			}

			/**
			 * Get the font associated with the given description.
			 */
			const Font_t& font(const FontDesc& fontDesc) const
			{
				Fonts::const_iterator i = m_fonts.find(fontDesc);
				if(i == m_fonts.end())
					// There is no font with the requested description
					BOOSTEXT_THROW(font_missing_exception(fontDesc));

				return *i;
			}

			/**
			 * Get a specific WidgetStyle.
			 */
			WidgetStyle::ptr widgetStyle(WidgetStyleIdentifier styleId) const
			{
				// We traverse the entire hierarchy of the widget style, trying to find the best match first.
				// A best match is defined as the identifier with the most parents.
				//
				// Examples:
				// If there is a style ('*', Text)('*', Desktop) and ('*', Text) present, then searching
				// for ('*', Text)('*', Desktop) will ALWAYS return the match with 1 parent, not the one with none
				//
				// However if there is a style ('*', Widget)('*', Desktop) and ('*', Text), then searching for
				// ('*', Text)('*', Desktop) will ALWAYS return the match with 1 parent, because the inheritance hierarchy
				// is traversed as well.
				WidgetStyles::const_iterator i = m_widgetStyles.end();
				while(styleId.size() && i == m_widgetStyles.end())
				{
					// We try to find a close match by first trying the original identifier, and then
					// modifying it's type to be the current type's parent (ie. querying for
					// ('*', Text) becomes ('*', Widget) in the second run), until there is no more
					// parent in the hierarchy.
					//
					// TODO: Evaluate possibility to do this for the entire hierarchy, and not only the front
					//       key
					Widgets::Type originalType = styleId.front().type;
					Widgets::Type type = originalType;
					while(type != Widgets::None && i == m_widgetStyles.end())
					{
						styleId.front().type = type;
						i = m_widgetStyles.find(styleId);
						type = Widgets::parent(type);
					}

					styleId.front().type = originalType;
					styleId.pop_back();
				}

				if(i == m_widgetStyles.end())
					BOOSTEXT_THROW(boostext::exception("That WidgetStyle does not exist"));

				return i->second;
			}



			/**
			 * Add a glyph to this style.
			 */
			void add(const Glyph_t& glyph)
			{
				Glyphs::const_iterator i = m_glyphs.find(glyph.name());
				if(i != m_glyphs.end())
					BOOSTEXT_THROW(boostext::exception(boostext::format("There is already a glyph with the name '%1%'") % glyph.name()));

				m_glyphs.insert(glyph.name(), glyph);
			}

			/**
			 * Remove a glyph from this style definition.
			 */
			void remove(QString name)
			{
				Glyphs::iterator i = m_glyphs.find(name);
				if(i == m_glyphs.end())
					BOOSTEXT_THROW(boostext::exception(boostext::format("There is no glyph with the name '%1%'") % name));

				m_glyphs.erase(i);
			}

			/**
			 * Add a font to this style.
			 */
			void add(const Font_t& font)
			{
				Fonts::const_iterator i = m_fonts.find(font.desc());
				if(i != m_fonts.end())
					BOOSTEXT_THROW(boostext::exception("There is already a font with the given parameters"));

				m_fonts.insert(font.desc(), font);
			}

			/**
			 * Remove a font from this style.
			 */
			void remove(const FontDesc& d)
			{
				Fonts::iterator i = m_fonts.find(d);
				if(i == m_fonts.end())
					BOOSTEXT_THROW(boostext::exception("There is no font with the given description"));

				m_fonts.erase(i);
			}

			/**
			 * Add a default style for another widget subclass.
			 */
			void add(const WidgetStyleIdentifier& styleId, const WidgetStyle::ptr& style)
			{
				WidgetStyles::const_iterator i = m_widgetStyles.find(styleId);
				if(i != m_widgetStyles.end())
					BOOSTEXT_THROW(boostext::exception("That style already exists"));

				m_widgetStyles.insert(std::make_pair(styleId, style));
			}
		};
		///////////////////////////////////////////////////////////////////////////////////////////

		typedef StyleDefinition<BasicFont,Glyph> BasicStyleDefinition;
		template class ENGINE_UI_API StyleDefinition<BasicFont,Glyph>;
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
